{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "996a90f8",
   "metadata": {},
   "source": [
    "# **二、序列标注**\n",
    "\n",
    "## 2.1 任务定义\n",
    "\n",
    "序列标注(sequence labeling)，就是对一个序列中的每一个token进行分类。它是许多自然语言处理问题的前驱，如情感分析，信息检索、推荐和过滤等等。同时在自然语言处理中，许多的任务可以转化为“将输入的语言序列转化为标注序列”来解决问题。\n",
    "\n",
    "输入输出：序列标注问题的输入是一个观测序列，输出是一个标记序列或状态序列。问题的目标在于学习一个模型，使它能够对观测序列给出标记序列作为预测。\n",
    "\n",
    "标注方式：序列标注的方法中有多种标注方式：BIO、BIOSE、IOB、BILOU、BMEWO，其中前三种最为常见。各种标注方法大同小异。下面列举一些常见的标签方案：\n",
    "\n",
    " 标签方案中通常都使用一些简短的英文字符[串]来编码。\n",
    "\n",
    " 标签是打在token上的。\n",
    "\n",
    " 对于英文，token可以是一个单词（e.g. awesome），也可以是一个字符（e.g. a）。\n",
    "\n",
    " 对于中文，token可以是一个词语（分词后的结果），也可以是单个汉字字符。\n",
    "\n",
    " 为便于说明，以下都将token试作等同于字符。\n",
    "\n",
    " 标签列表如下：\n",
    "\n",
    "- B，即Begin，表示开始\n",
    "- I，即Intermediate，表示中间\n",
    "- E，即End，表示结尾\n",
    "- S，即Single，表示单个字符\n",
    "- O，即Other，表示其他，用于标记无关字符\n",
    "\n",
    "1. BIO\n",
    "\n",
    "- B stands for 'beginning' (signifies beginning of an Named Entity, i.e. NE)\n",
    "- I stands for 'inside' (signifies that the word is inside an NE)\n",
    "- O stands for 'outside' (signifies that the word is just a regular word outside of an NE)   \n",
    "\n",
    "2. BIOES\n",
    "\n",
    "- B stands for 'beginning' (signifies beginning of an NE)\n",
    "- I stands for 'inside' (signifies that the word is inside an NE)\n",
    "- O stands for 'outside' (signifies that the word is just a regular word outside of an NE)\n",
    "- E stands for 'end' (signifies that the word is the end of an NE)\n",
    "- S stands for 'singleton'(signifies that the single word is an NE )\n",
    "\n",
    "3. IOB （即IOB-1）\n",
    "\n",
    " IOB与BIO字母对应的含义相同，其不同点是IOB中，标签B仅用于两个连续的同类型命名实体的边界区分，不用于命名实体的起始位置，这里举个例子：\n",
    "\n",
    " 词序列：（word）（word）（word）（word）（word）（word）\n",
    "\n",
    " IOB标注：（I-loc）（I-loc）（B-loc）（I-loc）（o）（o）\n",
    "\n",
    " BIO标注：（B-loc）（I-loc）（B-loc）（I-loc）（o）（o）\n",
    "\n",
    " The IOB scheme is similar to the BIO scheme,however, here the tag B- is only used to start a segment if the previous token is of the same class but is not part of the segment.\n",
    "\n",
    " 因为IOB的整体效果不好，所以出现了IOB-2，约定了所有命名实体均以B tag开头。这样IOB-2就与BIO的标注方式等价了。\n",
    "\n",
    "评价指标：常见的序列标注算法的模型效果评估指标有准确率（accuracy）、查准率(percision)、召回率(recall)、F1值等，计算的公式如下：\n",
    "\n",
    "- 准确率: accuracy = 预测对的元素个数/总的元素个数\n",
    "- 查准率：precision = 预测正确的实体个数 / 预测的实体总个数\n",
    "- 召回率：recall = 预测正确的实体个数 / 标注的实体总个数\n",
    "- F1值：F1 = 2 *准确率 * 召回率 / (准确率 + 召回率)\n",
    "\n",
    "## 2.2 NER\n",
    "\n",
    "命名实体识别（Named Entity Recognition，简称NER），是指识别文本中具有特定意义的实体，主要包括人名、地名、机构名、专有名词等。属于序列标注任务的范畴。\n",
    "\n",
    "### 2.2.1 传统方法\n",
    "\n",
    "基于规则，其思想在于在观察特定的领域文本以及实体出现的语法构成和模式的情况后，设计特定的实体提取规则以完成提取。\n",
    "\n",
    "实体词表、关系词或属性词触发词表、正则表达式是基于词典规则方法的三大核心部件，主要是2种方式：\n",
    "\n",
    "1. 基于实体词表的匹配识别\n",
    "\n",
    "基于实体词表的匹配识别是使用最广泛的一种实体识别方法，虽然实体词表实现目标文本词表的有限匹配，但见效十分快速。\n",
    "\n",
    "一般，在进行领域实体识别时，每个特定领域都有专属的实体词典，如医药行业的药名、科室名、手术名，汽车行业的车型、车系、品牌名称，金融行业中的公司词典、行业词典，招聘领域的职位词典等，这些词典都可以用来进行实体识别。\n",
    "\n",
    "对于有歧义的词汇，可以先进行分词，比如采用最大匹配法，在分词的基础上在进行NER任务。\n",
    "\n",
    "2. 基于规则模板的匹配识别\n",
    "\n",
    "规则模板可以实现对实体词表识别的扩展，其中的核心在于规则模板的设计，在此之前需要分析实体词或者属性值的构词规则，包括基于字符构词规则的识别以及基于词性组合规则的识别两种。其中，基于字符构词规则的识别采用正则表达式进行提取。例如：\n",
    "\n",
    "Email的表现形式通常为“ xxxx@xxx .com ”；利用“^\\w+([-+.]\\w+)@\\w+([-.]\\w+).\\w+([-.]\\w+)*$”来匹配Email地址，\n",
    "\n",
    "借助“\\d{4}[年-]\\d{1,2}[月-]\\d{1,2}日”的正则模板表达式来提取日期；\n",
    "\n",
    "### 2.2.2 CRF\n",
    "\n",
    "CRF，英文全称为conditional random field, 中文名为条件随机场，是给定一组输入随机变量条件下另一组输出随机变量的条件概率分布模型，其特点是假设输出随机变量构成马尔可夫（Markov）随机场。 较为简单的条件随机场是定义在线性链上的条件随机场，称为线性链条件随机场（linear chain conditional random field）. 线性链条件随机场可以用于序列标注等问题，这时，在条件概率模型P(Y|X)中，Y是输出变量，表示标记序列（或状态序列），X是输入变量，表示需要标注的观测序列。学习时利用训练数据集通过极大似然估计或正则化的极大似然估计得到条件概率模型P(Y|X)；预测时，对于给定的输入序列x，求出条件概率P(y|x)最大的输出序列y0.\n",
    "\n",
    "### 2.2.3 HMM\n",
    "\n",
    "隐马尔可夫模型（Hidden Markov Model，HMM），是一个统计模型。隐马尔可夫模型有三种应用场景，我们做命名实体识别只用到其中的一种——**求观察序列的背后最可能的标注序列**。\n",
    "\n",
    "HMM中，有5个基本元素：{N,M,A,B,π}，我结合序列标志任务对这5个基本元素做一个介绍：\n",
    "\n",
    "- N:状态的有限集合。在这里，是指每一个词语背后的标注。\n",
    "- M:观察值的有限集合。在这里，是指每一个词语本身。\n",
    "- A:状态转移概率矩阵。在这里，是指某一个标注转移到下一个标注的概率。\n",
    "- B:观测概率矩阵，也就是发射概率矩阵。在这里，是指在某个标注下，生成某个词的概率。\n",
    "- π:初始概率矩阵。在这里，是指每一个标注的初始化概率。\n",
    "\n",
    "而以上的这些元素，都是可以从训练语料集中统计出来的。最后，我们根据这些统计值，应用维特比（viterbi）算法，就可以算出词语序列背后的标注序列了。\n",
    "\n",
    "###  2.2.4 深度学习方法\n",
    "\n",
    "#### LSTM+CRF\n",
    "\n",
    "对于序列标注问题一个基于深度学习的方法便是BI-LSTM，简单的做法是将输入序列经过一个embeddig层转化为一个向量序列输入两个双向的LSTM单元，将每个时间序列的正向反向输出拼接，经过一个全连接层映射为一个维度为输出标签数量的一个向量，使用Softmax将输出归一化作为每种标签的概率。\n",
    "\n",
    "![](https://github.com/datawhalechina/hands-dirty-nlp/blob/main/hands-dirty-nlp/Chapter%202.%20%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8/output.png)\n",
    "\n",
    "首先我们要知道为什么使用LSTM+CRF，序列标注问题本质上是分类问题，因为其具有序列特征，所以LSTM就很合适进行序列标注，确实，我们可以直接利用LSTM进行序列标注。但是这样的做法有一个问题：每个时刻的输出没有考虑上一时刻的输出。我们在利用LSTM进行序列建模的时候只考虑了输入序列的信息，即单词信息，但是没有考虑标签信息，即输出标签信息。这样会导致一个问题，以“我 喜欢 跑步”为例，LSTM输出“喜欢”的标签是“动词”，而“跑步”的标签可能也是“动词”。但是实际上，“名词”标签更为合适，因为“跑步”这里是一项运动。也就是“动词”+“名词”这个规则并没有被LSTM模型捕捉到。也就是说这样使用LSTM无法对标签转移关系进行建模。而标签转移关系对序列标注任务来说是很重要的，所以就在LSTM的基础上引入一个标签转移矩阵对标签转移关系进行建模。这就和CRF很像了。我们知道，CRF有两类特征函数，一类是针对观测序列与状态的对应关系（如“我”一般是“名词”），一类是针对状态间关系（如“动词”后一般跟“名词”）。在LSTM+CRF模型中，前一类特征函数的输出由LSTM的输出替代，后一类特征函数就变成了标签转移矩阵。\n",
    "\n",
    "#### Bert+CRF\n",
    "\n",
    "BERT-CRF与BiLSTM-CRF模型较为相似，其本质上还是一个CRF模型。BERT模型+FC layer（全连接层）已经可以解决序列标注问题，以词性标注为例，BERT的encoding vector通过FC layer映射到标签集合后，单个token的output vector再经过Softmax处理，每一维度的数值就表示该token的词性为某一词性的概率。基于此数据便可计算loss并训练模型。但根据Bi-LSTM+CRF 模型的启发，我们在BERT+FC layer 的基础上增加CRF layer。 CRF是一种经典的概率图模型，具体数学原理不在此处展开。要声明的是，CRF层可以加入一些约束来保证最终的预测结果是有效的。这些约束可以在训练数据时被CRF层自动学习得到。具体的约束条件我们会在后面提及。有了这些有用的约束，错误的预测序列会大大减小。\n",
    "\n",
    "参考文献：\n",
    "\n",
    "https://zhuanlan.zhihu.com/p/268579769\n",
    "\n",
    "https://zhuanlan.zhihu.com/p/147537898\n",
    "\n",
    "http://nathanlvzs.github.io/Several-Tagging-Schemes-for-Sequential-Tagging.html"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
